﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Genetic : MonoBehaviour
{
    public static Genetic Instance
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = FindObjectOfType<Genetic>();
            }
            if (!_Instance)
            {
                GameObject obj = new GameObject("Genetic");
                _Instance = obj.AddComponent<Genetic>();
            }
            return _Instance;
        }
        set
        {
            _Instance = value;
        }
    }
    private static Genetic _Instance;

    private static int DATALEN = 15;
    private static int CROSSPROB = 50;
    private static int MUTPROB = 20;

    public static float hpOffset = 0.1f;
    public static float damageOffset = 0.1f;
    public static float attackSpeedOffset = 0.075f;
    public static float walkSpeedOffset = 0.05f;

    private List<SoldierData> data;
    private List<SoldierData> template;

    private SoldierData L, R;

    private int[] ascore = new int[DATALEN];
    int now = 0;
    int soldierCount = 0; //场上小兵数量

    public List<SoldierData> GetSoldierData()
    {
        return data;
    }

    public List<SoldierData> GetSoldierTemplate()
    {
        return template;
    }

    public int GetSoldierTag()
    {
        return now;
    }

    private void swap<T>(ref T a, ref T b)
    {
        T c = b;
        b = a;
        a = c;
    }

    private float range(float m, float l, float r)
    {
        m = System.Math.Max(m, l);
        m = System.Math.Min(m, r);
        return m;
    }

    private int get(int x)
    {
        int l = 0, r = DATALEN - 1;
        while(l < r)
        {
            int mid = (l + r + 1) >> 1;
            if (x >= ascore[mid])
                l = mid;
            else r = mid - 1;
        }
        return l;
    }

    private void select()
    {
        int[] p = new int[DATALEN];
        int count = 0;
        int top = 0;
        for (int i = 0; i < DATALEN; i++)
        {
            p[i] = data[i].score;
            count += p[i];
            if (p[i] > p[top])
                top = i;
            ascore[i] = count;
        }
        for (int i = 1; i < DATALEN; i++)
        {
            int f1 = get(RandomGenerator.Instance.Next(count)), f2 = get(RandomGenerator.Instance.Next(count));
            template.Add(p[f1] > p[f2] ? data[f1] : data[f2]);
        }
        template.Add(data[top]);
    }

    private void crossover()
    {
        for(int i = 0; i < DATALEN - 1; i++)
        {
            int other = RandomGenerator.Instance.Next(i);
            if (RandomGenerator.Instance.Next(100) < CROSSPROB)
            {
                swap(ref template[i].name, ref template[other].name);
            }
            if (RandomGenerator.Instance.Next(100) < CROSSPROB)
            {
                swap(ref template[i].hp, ref template[other].hp);
            }
            if (RandomGenerator.Instance.Next(100) < CROSSPROB)
            {
                swap(ref template[i].damage, ref template[other].damage);
            }
            if (RandomGenerator.Instance.Next(100) < CROSSPROB)
            {
                swap(ref template[i].attackSpeed, ref template[other].attackSpeed);
            }
            if (RandomGenerator.Instance.Next(100) < CROSSPROB)
            {
                swap(ref template[i].walkSpeed, ref template[other].walkSpeed);
            }
        }
    }

    private void mutation()
    {
        for (int i = 0; i < DATALEN - 1; i++)
        {
            if (RandomGenerator.Instance.Next(100) < MUTPROB)
            {
                template[i].name = RandomGenerator.Instance.Next(L.name, R.name);
            }
        }
        for(int i = 0; i < DATALEN - 1; i++)
        {
            if (RandomGenerator.Instance.Next(100) >= MUTPROB)
                continue;
            int kind = RandomGenerator.Instance.Next(4);
            bool ed = false;
            for(int j = 0; j < 4; j++)
            {
                switch (kind)
                {
                    case 0:
                        if (template[i].hp - hpOffset < L.hp)
                            break;
                        template[i].hp -= hpOffset;
                        ed = true;
                        break;
                    case 1:
                        if (template[i].damage - damageOffset < L.damage)
                            break;
                        template[i].damage -= damageOffset;
                        ed = true;
                        break;
                    case 2:
                        if (template[i].attackSpeed - attackSpeedOffset < L.attackSpeed)
                            break;
                        template[i].attackSpeed -= attackSpeedOffset;
                        ed = true;
                        break;
                    case 3:
                        if (template[i].walkSpeed - walkSpeedOffset < L.walkSpeed)
                            break;
                        template[i].walkSpeed -= walkSpeedOffset;
                        ed = true;
                        break;
                    default:
                        break;
                }
                if (ed)
                    break;
                kind = (kind + 1) % 4;
            }
            if(ed)
            {
                kind = RandomGenerator.Instance.Next(4);
                switch (kind)
                {
                    case 0:
                        template[i].hp = range(template[i].hp + hpOffset, L.hp, R.hp);
                        break;
                    case 1:
                        template[i].damage = range(template[i].damage + damageOffset, L.damage, R.damage);
                        break;
                    case 2:
                        template[i].attackSpeed = range(template[i].attackSpeed + attackSpeedOffset, L.attackSpeed, R.attackSpeed);
                        break;
                    case 3:
                        template[i].walkSpeed = range(template[i].walkSpeed + walkSpeedOffset, L.walkSpeed, R.walkSpeed);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    private void envolution()
    {
        for(int i = 0; i < DATALEN - 1; i++)
        {
            int kind = RandomGenerator.Instance.Next(4);
            switch (kind)
            {
                case 0:
                    template[i].hp = range(template[i].hp + hpOffset, L.hp, R.hp);
                    break;
                case 1:
                    template[i].damage = range(template[i].damage + damageOffset, L.damage, R.damage);
                    break;
                case 2:
                    template[i].attackSpeed = range(template[i].attackSpeed + attackSpeedOffset, L.attackSpeed, R.attackSpeed);
                    break;
                case 3:
                    template[i].walkSpeed = range(template[i].walkSpeed + walkSpeedOffset, L.walkSpeed, R.walkSpeed);
                    break;
                default:
                    break;
            }
        }
    }

    private void shuffle()
    {
        for(int i = 1; i < DATALEN; i++)
        {
            int other = RandomGenerator.Instance.Next(i);
            SoldierData mid = template[i];
            template[i] = template[other];
            template[other] = mid;
        }
    }

    public void addSoldier(SoldierData sd)
    {
        data.Add(sd);
        soldierCount--;
        if (soldierCount == 0)
        {
            MessageController.Instance.SendMsg("已自动保存！");
            SaveManager.Instance.SaveGame();
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        L = new SoldierData();
        R = new SoldierData();
        L.name = 0;
        L.hp = 0.6f;
        L.damage = 0.6f;
        L.attackSpeed = 0.7f;
        L.walkSpeed = 0.8f;
        R.name = SoldierManager.Instance.NumOfSoldier();
        R.hp = 7.0f;
        R.damage = 6.0f;
        R.attackSpeed = 4.0f;
        R.walkSpeed = 3.0f;
        soldierCount = 0;
        if (!ConstOfMap.loadgame)
        {
            now = 0;
            data = new List<SoldierData>();
            template = new List<SoldierData>();
            for (int i = 0; i < DATALEN; i++)
                template.Add(new SoldierData(RandomGenerator.Instance.Next(L.name, R.name)));
        }
        else
        {
            now = ConstOfMap.save.soldierTag;
            data = ConstOfMap.save.data;
            template = ConstOfMap.save.template;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if(data.Count >= DATALEN)
        {
            template.Clear();
            select();
            crossover();
            envolution();
            mutation();
            shuffle();
            data.Clear();
        }
    }

    public SoldierData GenerateSoldier()
    {
        if (template.Count != DATALEN)
            return null;
        now = now % DATALEN;
        soldierCount++;
        return template[now++];
    }
}
